/* 
 * $Id: digest.c 7557b5d4d82b 2012/12/02 09:07:16 pgurumur $
 * Copyright (c) 2009 Prabhu Gurumurthy <pgurumur@gmail.com>
 *  
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *  
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "digest.h"

/* Init the module, error and version */
void
initdigest()
{
   PyObject *module, *dict, *version;
   const char *Error = "digest.error";

   module = Py_InitModule("digest", PyDigestMethods);
   dict = PyModule_GetDict(module);

   version = PyString_FromString(PyDigestVersion);
   if (version == NULL || PyDict_SetItemString(dict, PyDigestVersion, version))
      Py_FatalError("could not initialize version string");

   Py_DECREF(version);

   DigestError = Py_BuildValue("s", Error);
   if (DigestError == NULL || 
         PyDict_SetItemString(dict, Error, DigestError))
      Py_FatalError("could not initialize digest error");

   Py_DECREF(DigestError);

   if (PyErr_Occurred())
      Py_FatalError("cannot initialize digest error");

   fflush(stdout);
   OpenSSL_add_all_digests();
}

static PyObject *
hmac_file(PyObject *self, PyObject *args)
{
   /* return value */
   PyObject *retval = NULL;

   /* EVP structure */
   EVP_MD_CTX mdctx;

   /* EVP algorithm structure, which can understand the digest algorithms */
   const EVP_MD *md;

   /* Digest value stored in unsigned char array */
   unsigned char md_value[EVP_MAX_MD_SIZE];

   /*
    * First argument is the algorithm, all HMAC algorithms supported by
    * OpenSSL are supported, any further additions by OpenSSL will
    * automatically be supported without any code changes,
    * If an unknown algorithm is supplied, error is raise, and empty
    * value is returned
    */
   const char *alg = NULL;

   /* 
    * Destination, can be file or a string, if a file is given, the module
    * tries to open the file, if the file cannot be opened, treats the 
    * file as a string, and returns the digest of the string
    */
   const char *dest = NULL;

   /* Get the arguments, */
   if (args)
      PyArg_ParseTuple(args, "ss", &alg, &dest);

   struct stat sb;
   memset(&sb, 0, sizeof(struct stat));

   if (stat(dest, &sb) == 0) {
      if (alg) {
         md = EVP_get_digestbyname(alg);
         if (md) {

            EVP_MD_CTX_init(&mdctx);
            EVP_DigestInit_ex(&mdctx, md, NULL);

            unsigned int md_len = 0, ix = 0;
            FILE *file = fopen(dest, "rb");
            size_t bytes = 0;

            if (file) {
               char buffer[BUFSIZ] = { 0 };

               while (!feof(file)) {
                  bytes = fread(buffer, 1, BUFSIZ, file);
                  if ((bytes < BUFSIZ) && ferror(file))
                     break;
                  else
                     EVP_DigestUpdate(&mdctx, buffer, bytes);
               }

               fclose(file);
            }
            EVP_DigestFinal_ex(&mdctx, md_value, &md_len);
            EVP_MD_CTX_cleanup(&mdctx);

            static char hex_digest[EVP_MAX_MD_SIZE * 2 + 1] = { 0 };
            for (ix = 0; ix < md_len; ix++)
               snprintf(hex_digest + 2 * ix, md_len, "%02x", md_value[ix]);

            hex_digest[sizeof(hex_digest) - 1] = '\0';
            retval = Py_BuildValue("s", hex_digest);
         }            
         else 
            PyErr_SetString(DigestError, "unknown message digest");
      }
      else
         PyErr_SetString(DigestError, "no hmac algorithm defined!");
   }
   else
      PyErr_SetString(DigestError, strerror(errno));

   return retval;
}


static PyObject *
hmac_string(PyObject *self, PyObject *args)
{
   /* return value */
   PyObject *retval = NULL;

   /* EVP structure */
   EVP_MD_CTX mdctx;

   /* EVP algorithm structure, which can understand the digest algorithms */
   const EVP_MD *md;

   /* Digest value stored in unsigned char array */
   unsigned char md_value[EVP_MAX_MD_SIZE];

   /*
    * First argument is the algorithm, all HMAC algorithms supported by
    * OpenSSL are supported, any further additions by OpenSSL will
    * automatically be supported without any code changes,
    * If an unknown algorithm is supplied, error is raise, and empty
    * value is returned
    */
   const char *alg = NULL;

   /* 
    * Destination, can be file or a string, if a file is given, the module
    * tries to open the file, if the file cannot be opened, treats the 
    * file as a string, and returns the digest of the string
    */
   const char *dest = NULL;

   /* Get the arguments, */
   if (args)
      PyArg_ParseTuple(args, "ss", &alg, &dest);

   if (dest) {
      if (alg) {
         md = EVP_get_digestbyname(alg);
         if (md) {

            EVP_MD_CTX_init(&mdctx);
            EVP_DigestInit_ex(&mdctx, md, NULL);

            unsigned int md_len = 0, ix = 0;
            EVP_DigestUpdate(&mdctx, dest, strlen(dest));
            EVP_DigestFinal_ex(&mdctx, md_value, &md_len);
            EVP_MD_CTX_cleanup(&mdctx);

            static char hex_digest[EVP_MAX_MD_SIZE * 2 + 1] = { 0 };
            for (ix = 0; ix < md_len; ix++)
               snprintf(hex_digest + 2 * ix, md_len, "%02x", md_value[ix]);

            hex_digest[sizeof(hex_digest) - 1] = '\0';
            retval = Py_BuildValue("s", hex_digest);
         }            
         else 
            PyErr_SetString(DigestError, "unknown message digest");
      }
      else
         PyErr_SetString(DigestError, "no hmac algorithm defined!");
   }
   else
      PyErr_SetString(DigestError, "no string defined!");

   return retval;
}
